package cn.dansj.common.processor;

import cn.dansj.common.processor.meta.WriteToResource;
import com.google.auto.service.AutoService;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.MirroredTypesException;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
import java.io.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@AutoService(Processor.class)
@SupportedAnnotationTypes("*") // 支持扫描所有注解
public class WriteToResourceProcessor extends AbstractProcessor {
    private static final boolean springboot2 = Integer.parseInt(Version.getSpringBootVersion().split("\\.")[0]) <= 2;
    private Filer filer;
    private Messager messager;
    private TypeElement writeToResourceType;

    // 全局缓存：存储需要合并写入的内容
    private final Map<String, Properties> springFactoriesCache = new ConcurrentHashMap<>();
    private final Map<String, Set<String>> importsCache = new ConcurrentHashMap<>();

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latest();
    }

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        this.filer = processingEnv.getFiler();
        this.messager = processingEnv.getMessager();
        writeToResourceType = processingEnv.getElementUtils().getTypeElement(WriteToResource.class.getCanonicalName());
    }

    private List<String> getKey(WriteToResource annotation) {
        try {
            // 主动触发异常以捕获 TypeMirror
            annotation.key();
            throw new IllegalStateException("Expected MirroredTypeException");
        } catch (MirroredTypesException e) {
            return e.getTypeMirrors().stream().map(TypeMirror::toString).filter(cls -> !cls.equals(Void.class.getName())).collect(Collectors.toList());
        }
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (roundEnv.processingOver()) { // 最后一轮处理执行写入
            writeAllCachedResources();
            return true;
        }

        if (writeToResourceType == null) {
            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "WriteToResource annotation not found");
            return true;
        }

        Map<TypeElement, WriteToResource> writeToResourceMap = new HashMap<>();

        // 第一步：处理元注解（被 @WriteToResource 标注的注解）
        for (TypeElement annotation : annotations) {
            if (processingEnv.getTypeUtils().isSameType(annotation.asType(), writeToResourceType.asType())) continue;
            WriteToResource writeToResource = annotation.getAnnotation(WriteToResource.class);
            if (writeToResource == null) continue;
            processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Found meta annotation : " + writeToResource + " from " + annotation);
            for (Element element : roundEnv.getElementsAnnotatedWith(annotation)) {
                if (element instanceof TypeElement && element.getKind() != ElementKind.ANNOTATION_TYPE) {
                    writeToResourceMap.put((TypeElement) element, writeToResource);
                }
            }
        }

        // 第二步：处理直接标注 @WriteToResource 的元素
        for (Element element : roundEnv.getElementsAnnotatedWith(WriteToResource.class)) {
            if (!(element instanceof TypeElement) || element.getKind() == ElementKind.ANNOTATION_TYPE) continue;
            TypeElement typeElement = (TypeElement) element;
            WriteToResource writeToResource = typeElement.getAnnotation(WriteToResource.class);
            if (writeToResource == null) continue;
            processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Found WriteToResource annotation : " + writeToResource);
            writeToResourceMap.put(typeElement, writeToResource);
        }

        for (Map.Entry<TypeElement, WriteToResource> entry : writeToResourceMap.entrySet()) {
            TypeElement typeElement = entry.getKey();
            WriteToResource writeToResource = entry.getValue();
            processAnnotation(typeElement, writeToResource);
        }
        return true;
    }

    private static boolean checkNull(String str) {
        return str == null || str.isEmpty();
    }

    private static String formatPath(String url) {
        return url.replaceAll("/+", "/").replaceAll("^/*|/*$", "") + "/";
    }

    private static String getMetaDirectoryPath(String directory, String defaultDirectory) {
        return formatPath("META-INF/" + (checkNull(directory) ? defaultDirectory : directory));
    }

    private void processAnnotation(TypeElement typeElement, WriteToResource writeToResource) {
        String className = typeElement.getQualifiedName().toString();

        try {
            TargetType type = TargetType.Configuration.equals(writeToResource.type()) ? springboot2 ? TargetType.SpringFactories : TargetType.Imports : writeToResource.type();
            if (type == TargetType.SpringFactories) {
                cacheSpringFactories(getMetaDirectoryPath(writeToResource.directory(), "") + "spring.factories", className, writeToResource, typeElement);
            } else {
                cacheImports(getMetaDirectoryPath(writeToResource.directory(), "spring"), className, writeToResource, typeElement);
            }
        } catch (IllegalArgumentException e) {
            messager.printMessage(Diagnostic.Kind.ERROR, e.getMessage(), typeElement);
        }
    }

    // 缓存Spring Factories数据
    private void cacheSpringFactories(String fileName, String className, WriteToResource writeToResource, TypeElement typeElement) {
        Properties props = springFactoriesCache.computeIfAbsent(fileName, k -> new Properties());
        getKeyName(writeToResource, typeElement).forEach(key -> {
            String existing = props.getProperty(key, "");
            String newValue = existing.isEmpty() ? className : existing + "," + className;
            props.put(key, newValue);
        });
    }

    // 缓存Imports数据
    private void cacheImports(String baseDir, String className, WriteToResource writeToResource, TypeElement typeElement) {
        getKeyName(writeToResource, typeElement).forEach(key -> {
            String fileName = baseDir + key + ".imports";
            importsCache.computeIfAbsent(fileName, k -> ConcurrentHashMap.newKeySet()).add(className);
        });
    }

    // 最终写入所有缓存内容
    private void writeAllCachedResources() {
        try {
            // 处理Spring Factories
            for (Map.Entry<String, Properties> entry : springFactoriesCache.entrySet()) {
                mergeAndWriteProperties(entry.getKey(), entry.getValue());
            }

            // 处理Imports文件
            for (Map.Entry<String, Set<String>> entry : importsCache.entrySet()) {
                writeImportsFile(entry.getKey(), entry.getValue());
            }
        } catch (IOException e) {
            messager.printMessage(Diagnostic.Kind.ERROR, "Failed to write resources: " + e.getMessage());
        }
    }

    // 合并并写入Properties文件
    private void mergeAndWriteProperties(String fileName, Properties newProps) throws IOException {
        Properties merged = new Properties();

        // 读取现有内容
        try {
            FileObject existingFile = filer.getResource(StandardLocation.CLASS_OUTPUT, "", fileName);
            try (InputStream is = existingFile.openInputStream()) {
                merged.load(is);
            }
        } catch (IOException ignored) {
        }

        // 合并新内容
        newProps.forEach((k, v) -> {
            String existing = merged.getProperty(k.toString(), "");
            String mergedValue = existing.isEmpty() ? v.toString() : existing + "," + v;
            merged.put(k, mergedValue);
        });

        // 写入文件
        FileObject file = filer.createResource(StandardLocation.CLASS_OUTPUT, "", fileName);
        try (OutputStream os = file.openOutputStream()) {
            merged.store(os, "Merged by WriteToResourceProcessor");
        }
    }

    // 写入Imports文件
    private void writeImportsFile(String fileName, Set<String> lines) throws IOException {
        Set<String> allLines = new TreeSet<>();

        // 读取现有内容
        try {
            FileObject existingFile = filer.getResource(StandardLocation.CLASS_OUTPUT, "", fileName);
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(existingFile.openInputStream()))) {
                reader.lines().forEach(allLines::add);
            }
        } catch (IOException ignored) {
        }

        // 合并新内容
        allLines.addAll(lines);

        // 写入文件
        FileObject file = filer.createResource(StandardLocation.CLASS_OUTPUT, "", fileName);
        try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(file.openOutputStream()))) {
            for (String line : allLines) {
                writer.write(line);
                writer.newLine();
            }
        }
    }

    private List<String> getKeyName(WriteToResource writeToResource, TypeElement typeElement) {
        List<String> keyName;
        List<String> keyClass = getKey(writeToResource);
        boolean defaultKeyClass = keyClass.isEmpty();
        List<String> keyProperty = Arrays.stream(writeToResource.keyProperty()).flatMap(e -> Stream.of(e.split("[,|;]"))).collect(Collectors.toList());
        boolean emptyKeyProperty = keyProperty.stream().allMatch(e -> e.trim().isEmpty());

        if (writeToResource.type().equals(TargetType.Configuration) && emptyKeyProperty && defaultKeyClass) {
            keyName = Collections.singletonList(springboot2 ? "org.springframework.boot.autoconfigure.EnableAutoConfiguration" : "org.springframework.boot.autoconfigure.AutoConfiguration");
        } else if (defaultKeyClass && emptyKeyProperty) {
            keyName = !typeElement.getInterfaces().isEmpty() ? getTypeElementInterfaces(typeElement) : !typeElement.getSuperclass().toString().equals(Object.class.getName()) ? Collections.singletonList(typeElement.getSuperclass().toString()) : null;
            if (keyName == null) {
                throw new IllegalArgumentException("keyProperty must be set when key is default and not implements one interface and not extend one superclass");
            }
            if (writeToResource.autoInterfacesSingleton()) keyName = Collections.singletonList(keyName.get(0));
        } else if (defaultKeyClass) {
            keyName = keyProperty;
        } else {
            keyName = keyClass;
        }

        return keyName;
    }

    private List<String> getTypeElementInterfaces(TypeElement typeElement) {
        return typeElement.getInterfaces().stream().map(typeMirror -> typeMirror instanceof DeclaredType ? ((TypeElement) ((DeclaredType) typeMirror).asElement()).getQualifiedName().toString() : typeMirror.toString()).collect(Collectors.toList());
    }
}